package com.cn.tous.auth.service;


import com.cn.tous.auth.oauth2.token.OidcIdTokenGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;

import java.time.Duration;

/**
 * @author 73578
 * @description 基于 Redis 实现 OAuth2AuthorizationService
 */

@AllArgsConstructor
@Slf4j
public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService {

    private RedisTemplate<String, Object> redisTemplate;

    private ObjectMapper objectMapper;

    private RegisteredClientRepository registeredClientRepository;

    private String issuer;

    private static final String KEY_PREFIX = "oauth2:authorization:";

    @Override
    public void save(OAuth2Authorization authorization) {
        String key = KEY_PREFIX + authorization.getId();
        try {
            // 序列化并存储授权信息
            String value = objectMapper.writeValueAsString(authorization);
            // 设置过期时间（取令牌最长有效期）
            long expireSeconds = getExpireSeconds(authorization);
            redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(expireSeconds));

            // 存储令牌与授权ID的映射
            saveTokenMapping(authorization, "access_token", authorization.getAccessToken().getToken().getTokenValue());
            if (authorization.getRefreshToken() != null) {
                saveTokenMapping(authorization, "refresh_token", authorization.getRefreshToken().getToken().getTokenValue());
            }
//            if (authorization.getAuthorizationCode().isPresent()) {
//                saveTokenMapping(authorization, "code", authorization.getAuthorizationCode().get().getTokenValue());
//            }
        } catch (Exception e) {
            throw new RuntimeException("保存授权信息失败", e);
        }
    }

    @Override
    public OAuth2Authorization findById(String id) {
        String key = KEY_PREFIX + id;
        return getAuthorizationFromRedis(key);
    }

    @Override
    public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
        String key = KEY_PREFIX + "token:" + tokenType.getValue() + ":" + token;
        String authorizationId = (String) redisTemplate.opsForValue().get(key);
        if (authorizationId == null) {
            return null;
        }
        return findById(authorizationId);
    }


    @Override
    public void remove(OAuth2Authorization authorization) {
        String key = KEY_PREFIX + authorization.getId();
        redisTemplate.delete(key);

        // 删除令牌映射
        removeTokenMapping(authorization, "access_token", authorization.getAccessToken().getToken().getTokenValue());
        if (authorization.getRefreshToken() != null) {
            removeTokenMapping(authorization, "refresh_token", authorization.getRefreshToken().getToken().getTokenValue());
        }
//        if (authorization.getAuthorizationCode().isPresent()) {
//            removeTokenMapping(authorization, "code", authorization.getAuthorizationCode().get().getTokenValue());
//        }
    }

    // 存储令牌与授权ID的映射
    private void saveTokenMapping(OAuth2Authorization authorization, String tokenType, String tokenValue) {
        String key = KEY_PREFIX + "token:" + tokenType + ":" + tokenValue;
        redisTemplate.opsForValue().set(
                key,
                authorization.getId(),
                Duration.ofSeconds(getExpireSeconds(authorization))
        );
    }

    // 删除令牌映射
    private void removeTokenMapping(OAuth2Authorization authorization, String tokenType, String tokenValue) {
        String key = KEY_PREFIX + "token:" + tokenType + ":" + tokenValue;
        redisTemplate.delete(key);
    }

    // 从Redis获取授权信息
    private OAuth2Authorization getAuthorizationFromRedis(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            return null;
        }
        try {
            String valueString = value.toString();
            OAuth2Authorization authorization = objectMapper.readValue(valueString, OAuth2Authorization.class);
            return restoreRegisteredClient(authorization);
        } catch (Exception e) {
            log.error("解释auther失败", e);
            throw new RuntimeException("解析授权信息失败", e);
        }
    }

    // 计算过期时间（秒）
    private long getExpireSeconds(OAuth2Authorization authorization) {
        // 默认1小时
        long expire = 3600;
        if (authorization.getRefreshToken() != null) {
            expire = Duration.between(
                    authorization.getRefreshToken().getToken().getIssuedAt(),
                    authorization.getRefreshToken().getToken().getExpiresAt()
            ).getSeconds();
        }
        return expire;
    }
    private OAuth2Authorization restoreRegisteredClient(OAuth2Authorization authorization) {

        String clientId = authorization.getRegisteredClientId();
        RegisteredClient client = registeredClientRepository.findById(clientId);

        if (client == null) {
            throw new IllegalArgumentException("RegisteredClient not found for client_id: " + clientId);
        }
        OidcIdToken oidcIdToken =  OidcIdTokenGenerator.generateIdToken(authorization.getPrincipalName(), clientId, "", authorization.getAccessToken().getToken(), issuer);
        // 使用 mutate() 创建新实例，补全 client
        return OAuth2Authorization.withRegisteredClient(client).id(authorization.getId())
                .principalName(authorization.getPrincipalName())
                .authorizationGrantType(authorization.getAuthorizationGrantType())
                .attributes(attrs -> attrs.putAll(authorization.getAttributes()))
                .accessToken(authorization.getAccessToken().getToken())
                .refreshToken(authorization.getRefreshToken().getToken())
                .token(oidcIdToken)
                .authorizedScopes(authorization.getAuthorizedScopes())
                .build();

    }
}
